package edu.lambdateacher.awt;

/*
 LambdaTeacher
 Copyright (C) 2000  Kim Mason
 Copyright (C) 2014  Vitalij Zadneprovskij

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/*
 A Lambda Calculus Teaching tool.  main class is LambdaTeacher, with no arguments.
 Author: Kim Mason 1997.  Written in Sun Java 1.1.6.  That's 100% Pure Java, Son!
 Just Say "NO" to MicroSoft Poo.

 This is a program to interpret Lambda Expressions, and give real time output to the
 user while interpreting the expressions.
 */
import java.awt.AWTException;
import java.awt.Button;
import java.awt.Checkbox;
import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Label;
import java.awt.List;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;

import org.apache.log4j.Logger;
import org.omg.CORBA.Environment;

import edu.lambdateacher.InterpreterFacade;
import edu.lambdateacher.InterpreterResponseBean;
import edu.lambdateacher.PropertiesManager;
import edu.lambdateacher.expressions.Binding;
import edu.lambdateacher.expressions.Expression;
import edu.lambdateacher.parser.ParseException;

/**
 * The class LambdaTeacher contains the main() function.
 */
public class AWTUserInterface extends Frame {
	/**
	 * 
	 */
	private static final long serialVersionUID = -3238217153373705658L;

	private final static Logger LOGGER = Logger.getLogger(AWTUserInterface.class);
	
	Map<String, Expression> boundExpressionMap = new HashMap<String, Expression>();

	// The Menubar and Menus
	MenuBar menubar = null;
	Menu file = null;
	MenuItem openitem = null;

	// fileopenactionlistener is the class which listens for a "file open"
	// action event and is registered as a listener with openitem
	ActionListener fileopenactionlistener = new FileOpenActionListener(this);

	MenuItem quititem = null;

	InterpreterFacade interpreterFacade=new InterpreterFacade();

	// filequitactionlistener listens for a "file quit" action event and
	// is registered as a listener with quititem
	ActionListener filequitactionlistener = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			AWTUserInterface.this.setVisible(false);
			AWTUserInterface.this.dispose();
			System.exit(0);
		}
	};
	
	public void callInterpreter(Reader inreader, Boolean fromfile){
		InterpreterResponseBean interpreterResponseBean = null;
		try {
			interpreterResponseBean = interpreterFacade.enterExpression(inreader, fromfile);
		} catch (ParseException e) {
			LOGGER.error(e);
			this.theexpr=null;
			this.exprdisplay.setText("Parsing error.");
			return;
		}
		LOGGER.debug("Interpreter returned " + interpreterResponseBean);
		this.theexpr=interpreterResponseBean.getExpression();
		this.exprdisplay.setText(interpreterResponseBean.getExpressionToDisplay());
	}

	// GUI Components and Layout managers

	// a TextField for the user to enter text
	TextField inputfield = null;
	// inputfields event handler
	ActionListener inputfieldactionlistener = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			// Someone hit enter in the textfield
			StringReader instread = new StringReader(
					(String) e.getActionCommand());
			callInterpreter(instread, false);
		}
	};
	TextArea exprdisplay = null;
	TextArea infoarea = null;
	Panel panel1 = null, panel2 = null, panel3 = null;

	// this is the button to reduce any expression
	Button anyreducebtn = null;
	// this is its event handler
	ActionListener anyreducebtnactionlistener = new AnyReduceBtnActionListener(this);
	// the buttons for normal order reduction
	Button normalbtn = null;

	// an event handler to handle action events on the normalbtn
	ActionListener normalbtnactionlistener = new NormalBtnActionListener(this);

	// the button for applicative order reduction
	Button applicativebtn = null;

	// an event handler to handle action events on the normalbtn
	ActionListener applicativebtnactionlistener = new ApplicativeBtnActionListener(this);

	Button completenormalbtn = null;
	// an event handler for the completenormalbtn
	ActionListener completenormalbtnactionlistener = new CompleteNormalBtnActionListener(this);

	Button removeallbindingsbtn = null;

	ActionListener removeallbindingsbtnactionlistener = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			if (theexpr == null){
				return;
			}
			theexpr = theexpr.removeAllBindings();
			String lambdastring = null;
			if (theexpr != null) {
				lambdastring = theexpr.stringRepresentation(false);
			}
			if (lambdastring != null) {
				exprdisplay.setText(lambdastring);
			} else {
				exprdisplay.setText("");
			}
		}
	};

	Button removeanybindingsbtn = null;
	ActionListener removeanybindingsbtnactionlistener = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			if (theexpr != null) {
				ExpressionChooser thechooser = new ExpressionChooser(theexpr,
						AWTUserInterface.this);
				thechooser.setVisible(true);
				Expression toremove = thechooser.getExpr();
				if (toremove instanceof Binding) {
					theexpr = theexpr.removeBinding(((Binding) toremove));
				} else {
					if (toremove != null) {
						String message = "The expression you selected is not a name binding.";
						MessageDialog toshow = new MessageDialog(
								AWTUserInterface.this, "No Binding", message);
						toshow.setVisible(true);
					}
				}
			}

			String lambdastring = null;
			if (theexpr != null) {
				lambdastring = theexpr.stringRepresentation(false);
			}
			if (lambdastring != null) {
				exprdisplay.setText(lambdastring);
			} else {
				exprdisplay.setText("");
			}
		}
	};

	// a check box for turning help on and off
	Checkbox helpbox = null;

	// the handler for the help check box
	ItemListener helpboxitemlistener = new ItemListener() {
		public void itemStateChanged(ItemEvent e) {
			PropertiesManager.expdlogon = !(PropertiesManager.expdlogon);
			AWTUserInterface.this.helpbox.setState(PropertiesManager.expdlogon);
		}
	};

	// A textual list of bindings which have been entered into the environment
	// so far.
	private List bindinglist = null;
	public List getBindinglist() {
		return bindinglist;
	}

	public void setBindinglist(List bindinglist) {
		this.bindinglist = bindinglist;
	}

	ItemListener bindinglistitemlistener = new ItemListener() {
		public void itemStateChanged(ItemEvent event) {
			String bindingstring = "";

			if (event.getItem() != null) {
				// lookup the selected element in the environment
				Expression thebinding = boundExpressionMap.get(bindinglist
						.getItem(((Integer) event.getItem()).intValue()));
				// now try and unparse thebinding's expression
				if (thebinding instanceof Binding) {
					if (((Binding) thebinding).getExpr() != null)
						bindingstring = ((Binding) thebinding).getExpr()
								.stringRepresentation(false);
				} else if (thebinding != null) {
					bindingstring = thebinding.stringRepresentation(false);
				} else {
					bindingstring = "Error - Binding not found in Environment.";
				}
			}
			// set the text in bindingdisplay
			bindingdisplay.setText(bindingstring);
		}
	};

	TextArea bindingdisplay = null;

	// The current expression, and the toplevel environment
	Environment toplevel = null;
	private Expression theexpr = null;

	// an event handler to handle window events
	WindowAdapter windoweventhandler = new WindowAdapter() {
		public void windowClosed(WindowEvent e) {
			AWTUserInterface.this.setVisible(false);
			AWTUserInterface.this.dispose();
			System.exit(0);
		}

		public void windowClosing(WindowEvent e) {
			AWTUserInterface.this.setVisible(false);
			AWTUserInterface.this.dispose();
			System.exit(0);
		}
	};

	/**
	 * The LambdaTeacher constructor method. It sets up the GUI, and initialises
	 * the top level environment.
	 */
	public AWTUserInterface() {
		// Create the frame (call the superclass constructor)
		super("Lambda Calculus Teaching Tool");
		
		// handle any windowevents
		this.addWindowListener(windoweventhandler);
		// Set up the menu bar
		menubar = new MenuBar();
		this.setMenuBar(menubar);
		// Create the file menu and add it to the menubar
		file = new Menu("File");
		openitem = new MenuItem("Open");
		openitem.addActionListener(fileopenactionlistener);
		file.add(openitem);

		quititem = new MenuItem("Quit");
		quititem.addActionListener(filequitactionlistener);
		file.add(quititem);
		menubar.add(file);

		// Create a new textfield for entering Lambda expressions into
		inputfield = new TextField(64);
		inputfield.addActionListener(inputfieldactionlistener);
		inputfield.setEditable(true);

		// Create a new textarea for displaying the current working expression
		exprdisplay = new TextArea("", 1, 40, TextArea.SCROLLBARS_VERTICAL_ONLY);
		exprdisplay.setEditable(false);

		// Create a new textarea for output
		infoarea = new TextArea(6, 40);
		infoarea.setEditable(false);

		anyreducebtn = new Button("Reduce Any Expression One Step");
		anyreducebtn.addActionListener(anyreducebtnactionlistener);

		normalbtn = new Button("Normal Reduce 1 Step");
		normalbtn.addActionListener(normalbtnactionlistener);
		completenormalbtn = new Button("Completely Normal Reduce");
		completenormalbtn.addActionListener(completenormalbtnactionlistener);
		applicativebtn = new Button("Applicative Reduce 1 Step");
		applicativebtn.addActionListener(applicativebtnactionlistener);
		removeallbindingsbtn = new Button("Remove All Name Bindings");
		removeallbindingsbtn
				.addActionListener(removeallbindingsbtnactionlistener);
		removeanybindingsbtn = new Button("Remove Any Name Bindings");
		removeanybindingsbtn
				.addActionListener(removeanybindingsbtnactionlistener);

		helpbox = new Checkbox("Help On/Off", PropertiesManager.expdlogon);
		helpbox.addItemListener(helpboxitemlistener);

		bindinglist = new List(4, false);
		bindinglist.addItemListener(bindinglistitemlistener);
		bindingdisplay = new TextArea("", 1, 36,
				TextArea.SCROLLBARS_HORIZONTAL_ONLY);
		bindingdisplay.setEditable(false);

		// Use a Gridbaglayout to layout the components
		panel1 = new Panel();
		panel1.setLayout(new GridBagLayout());

		// Now constrain all of the components into their panels, and constrain
		// the panels
		// into the frame. All constrain does is add them in a GridBagLayout.
		try {
			constrain(panel1, new Label(
					"Enter Lambda Expression: (hit Enter when finished)"), 0,
					0, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.NORTHWEST, 1.0, 0.0, 0, 0, 0, 0);
			constrain(panel1, inputfield, 0, 1, 1, 1,
					GridBagConstraints.HORIZONTAL,
					GridBagConstraints.NORTHWEST, 1.0, 0.0, 0, 0, 10, 0);
			constrain(panel1, new Label("Current Working Lambda Expression:"),
					0, 2, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.NORTHWEST, 1.0, 0.0, 0, 0, 0, 0);
			constrain(panel1, exprdisplay, 0, 3, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.NORTHWEST, 1.0, 1.0, 0, 0, 10, 0);
			constrain(panel1, new Label("Messages:"), 0, 4, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.NORTHWEST, 1.0,
					0.0, 0, 0, 0, 0);
			constrain(panel1, infoarea, 0, 5, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.NORTHWEST, 1.0, 1.0, 0, 0, 0, 0);

			panel2 = new Panel();
			panel2.setLayout(new GridBagLayout());
			constrain(panel2, anyreducebtn, 0, 0, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.CENTER, 1.0,
					1.0, 5, 10, 5, 10);
			constrain(panel2, new Label("Automated Reductions"), 0, 1, 1, 1,
					GridBagConstraints.NONE, GridBagConstraints.CENTER, 0.0,
					0.0, 5, 10, 0, 10);
			constrain(panel2, normalbtn, 0, 2, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.CENTER, 1.0, 1.0, 5, 10, 5, 10);
			constrain(panel2, completenormalbtn, 0, 3, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.CENTER, 1.0,
					1.0, 5, 10, 5, 10);
			constrain(panel2, applicativebtn, 0, 4, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.CENTER, 1.0,
					1.0, 5, 10, 5, 10);
			constrain(panel2, new Label("Name Binding Operations"), 0, 5, 1, 1,
					GridBagConstraints.NONE, GridBagConstraints.CENTER, 0.0,
					0.0, 5, 10, 0, 10);
			constrain(panel2, removeallbindingsbtn, 0, 6, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.CENTER, 1.0,
					1.0, 5, 10, 5, 10);
			constrain(panel2, removeanybindingsbtn, 0, 7, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.CENTER, 1.0,
					1.0, 5, 10, 5, 10);
			constrain(panel2, helpbox, 0, 8, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.CENTER, 1.0, 1.0, 5, 10, 5, 10);

			panel3 = new Panel();
			panel3.setLayout(new GridBagLayout());
			constrain(panel3, new Label("Binding list:"), 0, 0, 1, 1,
					GridBagConstraints.NONE, GridBagConstraints.CENTER, 0.0,
					0.0, 5, 10, 0, 10);
			constrain(panel3, bindinglist, 0, 1, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.CENTER, 1.0, 1.0, 5, 10, 5, 10);
			constrain(panel3, bindingdisplay, 0, 2, 1, 1,
					GridBagConstraints.BOTH, GridBagConstraints.CENTER, 1.0,
					1.0, 5, 10, 5, 10);

			// Now use constrain to arrange the panels within the frame
			this.setLayout(new GridBagLayout());
			constrain(this, panel1, 0, 0, 1, 2, GridBagConstraints.BOTH,
					GridBagConstraints.CENTER, 1.0, 1.0, 0, 0, 0, 0);
			constrain(this, panel2, 1, 0, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.CENTER, 1.0, 0.0, 0, 0, 0, 0);
			constrain(this, panel3, 1, 1, 1, 1, GridBagConstraints.BOTH,
					GridBagConstraints.CENTER, 1.0, 1.0, 0, 0, 0, 0);
		} catch (AWTException e1) {
			LOGGER.error("Error setting up GUI - Aborting.",e1);
			this.dispose();
			System.exit(0);
		}
		this.pack();
		this.setVisible(true);

	}

	/**
	 * Error is a small method for printing error messages into the textfield
	 */
	public void error(String inerror) {
		if (inerror != null)
			infoarea.append(inerror);
	}





	/**
	 * Here is the main constrain() method. It has arguments for all
	 * constraints. This constrains for GridBagLayout.
	 */
	static public void constrain(Container container, Component component,
			int grid_x, int grid_y, int grid_width, int grid_height, int fill,
			int anchor, double weight_x, double weight_y, int top, int left,
			int bottom, int right) throws AWTException {
		// we can't constrain a non GridBagLayout.
		if (!(container.getLayout() instanceof GridBagLayout)){
			throw new AWTException("Attempt to constrain a non GridBagLayout");
		}

		GridBagConstraints c = new GridBagConstraints();
		c.gridx = grid_x;
		c.gridy = grid_y;
		c.gridwidth = grid_width;
		c.gridheight = grid_height;
		c.fill = fill;
		c.anchor = anchor;
		c.weightx = weight_x;
		c.weighty = weight_y;
		if (top + bottom + left + right > 0)
			c.insets = new Insets(top, left, bottom, right);

		((GridBagLayout) container.getLayout()).setConstraints(component, c);
		container.add(component);
	}

	/**
	 * This version of constrain specifies the position of a component that
	 * doesn't grow and doesn't have margins
	 */
	static public void constrain(Container container, Component component,
			int grid_x, int grid_y, int grid_width, int grid_height)
			throws AWTException {
		constrain(container, component, grid_x, grid_y, grid_width,
				grid_height, GridBagConstraints.NONE,
				GridBagConstraints.NORTHWEST, 0.0, 0.0, 0, 0, 0, 0);
	}

	/**
	 * This version of constrain specifies the position of a component that
	 * doesn't grow, but does have margins
	 */
	static public void constrain(Container container, Component component,
			int grid_x, int grid_y, int grid_width, int grid_height, int top,
			int left, int bottom, int right) throws AWTException {
		constrain(container, component, grid_x, grid_y, grid_width,
				grid_height, GridBagConstraints.NONE,
				GridBagConstraints.NORTHWEST, 0.0, 0.0, top, left, bottom,
				right);
	}

	/**
	 * This is the main method. It simply creates a new LambdaTeacher
	 * 
	 * @throws UnsupportedLookAndFeelException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws ClassNotFoundException
	 */
	public static void main(String argv[]) throws ClassNotFoundException,
			InstantiationException, IllegalAccessException,
			UnsupportedLookAndFeelException {
		for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
			if ("Nimbus".equals(info.getName())) {
				UIManager.setLookAndFeel(info.getClassName());
				break;
			}
		}

		// UIManager.setLookAndFeel(laf);

		AWTUserInterface mainteach = new AWTUserInterface();
		SwingUtilities.updateComponentTreeUI(mainteach);
	}

	public Expression getTheexpr() {
		return theexpr;
	}

	public void setTheexpr(Expression theexpr) {
		this.theexpr = theexpr;
	}

	
	

}
